<?php
/**
 * ErrorHook
 * 
 * @author Alexey Strelnikoff <zog@zoggi.net>
 */

namespace Zoggi\ErrorHook;

/**
 * Класс для обработки ошибок PHP.
 */
class ErrorCatcher {

	/**
	 * Список нотификаторов, которые вызовутся при возникновении любой ошибки
	 * 
	 * @var array
	 */
	public $notifiers = array();

	/**
	 * Массив ошибок
	 * @var array
	 */
	private $errors = array();

	/**
	 * Функция, вызываемая, когда PHP не хватает памяти
	 * @var Closure 
	 */
	private $memoryOutCallback = NULL;

	/**
	 * Определяет нужно-ли вызывать shutdown функцию
	 * @var boolean
	 */
	private $active = true;

	/**
	 * Конструктор
	 * 
	 * @return ErrorCatcher
	 */
	public function __construct() {
		// Так после вызова shutdownFunction никаких подключений (include|require) 
		// не сделать, и может возникнуть ситуация, что ErrorUnit не подгружен.
		// По-этому, мы заранее подключим класс для обработки ошибок.
		// А чтобы не путать схему подключения файлов, этим будет заниматься autloader.php
		// @link http://www.php.net/manual/ru/function.class-exists.php
		class_exists(__NAMESPACE__.'\\ErrorUnit');
	}

	/**
	 * Оброботчик ошибок, вызывается каждый раз когда происходит ошибка
	 * 
	 * @see http://php.net/manual/en/function.set-error-handler.php
	 * 
	 * @param int $errno
	 * @param string $errstr
	 * @param string $file
	 * @param int $line
	 * @return void
	 */
	public function errorHandler( $errno, $errstr, $file, $line ) {

		$error = new ErrorUnit($errno, $errstr, $file, $line);
		$this->errors[] = $error;
		foreach ( $this->notifiers as $notifier ) {
			$notifier->notifyOne($error);
		}
	}

	/**
	 * Вызывается в момент остановки скрипта, и при необходимости отправляет ошибки
	 * А так же ловит фатальные ошибки
	 *
	 * @return void
	 */
	public function shutdownFunction() {

		if ( !$this->active ) {
			return;
		}
		// коды фатальных ошибок
		$fatals = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR);

		// catch FATAL error
		$error = error_get_last();
		if ( in_array($error['type'], $fatals) ) {
			// check if Fatal error is a 'Allowed memory size of ...'
			if ( strstr($error['message'], 'Allowed memory size of') !== false ) {
				if ( is_callable($this->memoryOutCallback) ) {
					call_user_func($this->memoryOutCallback, $error);
				}
				exit();
			}
			$this->errorHandler($error['type'], $error['message'], $error['file'], $error['line']);
		}

		foreach ( $this->notifiers as $notifier ) {
			$notifier->notifyAll($this->errors);
		}
	}

	/**
	 * Добавляет нотификатор к списку нотификаторов для каждой ошибки
	 * 
	 * @param NotifierInterface $notifier
	 */
	public function addNotifier( NotifierInterface $notifier ) {
		$this->notifiers[] = $notifier;
	}

	/**
	 * Устанавливает обработчик выполняемый, когда произошла ошибка
	 * E_FATAL Allowed memory size of ...
	 * 
	 * В этом случае у PHP нет памяти, для создания полноценных нотификаторов, 
	 * или других объектов, однако, есть небольшой трюк!
	 * Из-за того, что PHP выделяет память блоками, после возникновения ошибки, 
	 * остаётся ещё несколько байт, которые можно использоваться для вызова 
	 * своей функции.
	 * 
	 * @param callable $callbackFunction
	 */
	public function setMemoryOutCallback( $callbackFunction ) {
		$this->memoryOutCallback = $callbackFunction;
	}

	/**
	 * Отключает shutdownFunction
	 */
	public function disable() {
		$this->active = false;
	}

}